// 
// Copyright (c) 2006-2009 Ben Motmans
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// Author(s):
//    Ben Motmans <ben.motmans@gmail.com>
//

using System;
using System.IO;
using System.Reflection;
using System.Collections.Generic;

namespace Anculus.Core
{
	/// <summary>
	/// Configuration Utility.
	/// 
	/// This utility provides the basics for every application in need of a config file.
	/// </summary>
	public static class Config
	{
		private static IConfiguration config;

		/// <summary>
		/// Initializes this Configuration.
		/// </summary>
		public static void Initialize ()
		{
			Assembly ass = Assembly.GetExecutingAssembly ();
			
			AssemblyCompanyAttribute company = GetAssemblyAttribute<AssemblyCompanyAttribute> (ass);
			AssemblyProductAttribute product = GetAssemblyAttribute<AssemblyProductAttribute> (ass);
			AssemblyTitleAttribute title = GetAssemblyAttribute<AssemblyTitleAttribute> (ass);
			
			string companyName = company != null ? GenerateValidName (company.Company) : null;
			string appName = product != null ? GenerateValidName (product.Product) : title != null ? GenerateValidName (title.Title) : null;
			
			if (appName == null) {
				//not defined in the assembly attributes, guess based on the filename
				
				string location = ass.Location;
				if (!ass.ReflectionOnly && !String.IsNullOrEmpty (location))
					appName = GenerateValidName (Path.GetFileNameWithoutExtension (location));
				
				if (appName == null) {
					//if still null, fail horribly
					throw new InvalidOperationException ("Unable to guess the application name, call Config.Initialize (string name) instead or define an assembly product or title attribute.");
				}
			}
			
			Initialize (companyName, appName);
		}

		/// <summary>
		/// Initializes the Configuration.
		/// </summary>
		/// <param name="appName">Name of the app.</param>
		public static void Initialize (string appName)
		{
			Initialize (null, appName);
		}

		/// <summary>
		/// Initializes the Configuration
		/// </summary>
		/// <param name="companyName">Name of the company.</param>
		/// <param name="appName">Name of the app.</param>
		public static void Initialize (string companyName, string appName)
		{
			if (String.IsNullOrEmpty (appName))
				throw new ArgumentNullException ("appName");
			
			if (!IsInitialized) {
				CompanyName = companyName;
				ApplicationName = appName;
				
				if (UseApplicationDirectory) {
					Assembly ass = Assembly.GetExecutingAssembly ();
					if (ass.ReflectionOnly || ass.Location == String.Empty)
						Directory = Environment.CurrentDirectory; //not as good, since the current dir could already be different then the .exe
					else
						Directory = Path.GetFullPath (Path.GetDirectoryName (ass.Location));
				} else {
					string appdata = Path.GetFullPath (Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData));
				
					if (!String.IsNullOrEmpty (companyName))
						Directory = Path.Combine (Path.Combine (appdata, companyName), appName);
					else
						Directory = Path.Combine (appdata, appName);
				}
				
				//don't catch if it throws 
				if (!System.IO.Directory.Exists (Directory))
					System.IO.Directory.CreateDirectory (Directory);
				
				FileName = Path.Combine (Directory, "config.xml");
				
				config = new XmlConfiguration (FileName);
				config.Load ();
				
				IsInitialized = true;
			}
		}

		/// <summary>
		/// Gets a value indicating whether this instance is initialized.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this instance is initialized; otherwise, <c>false</c>.
		/// </value>
		public static bool IsInitialized { get; private set; }

		/// <summary>
		/// Gets a value indicating whether the application startup directory is used to store the config file.
		/// This is useful when your application runs on a USB stick.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if the application startup directory is used to store the config file; otherwise, <c>false</c>.
		/// </value>
		public static bool UseApplicationDirectory { get; set; }

		/// <summary>
		/// Gets the name of the company.
		/// </summary>
		/// <value>The name of the company.</value>
		public static string CompanyName { get; private set; }

		/// <summary>
		/// Gets the name of the application.
		/// </summary>
		/// <value>The name of the application.</value>
		public static string ApplicationName { get; private set; }

		/// <summary>
		/// Gets the configuration directory.
		/// </summary>
		/// <value>The directory.</value>
		public static string Directory { get; private set; }

		/// <summary>
		/// Gets the absolute path of the config file.
		/// </summary>
		/// <value>The name of the file.</value>
		public static string FileName { get; private set; }

		/// <summary>
		/// Gets the specified configuration section.
		/// </summary>
		/// <param name="section">The section name.</param>
		/// <returns></returns>
		public static IConfigurationSection GetSection (string section)
		{
			return config[section];
		}

		/// <summary>
		/// Gets the main section.
		/// </summary>
		/// <value>The main section.</value>
		public static IConfigurationSection Main
		{
			get { return config["main"]; }
		}

		/// <summary>
		/// Gets the plugins section.
		/// </summary>
		/// <value>The plugins section.</value>
		public static IConfigurationSection Plugins
		{
			get { return config["plugins"]; }
		}

		/// <summary>
		/// Saves the config.
		/// </summary>
		public static void Save ()
		{
			if (IsInitialized)
				config.Save ();
		}

		/// <summary>
		/// Gets the assembly attribute.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="ass">The ass.</param>
		/// <returns></returns>
		private static T GetAssemblyAttribute<T> (Assembly ass)
		{
			object[] attribs = ass.GetCustomAttributes (typeof (T), false);
			if (attribs.Length > 0)
				return (T)attribs[0];
			return default (T);
		}

		/// <summary>
		/// Generates a valid filename.
		/// </summary>
		/// <param name="name">The name.</param>
		/// <returns></returns>
		private static string GenerateValidName (string name)
		{
			//not very efficient, but this function is called only 2 times (at most)
			
			char[] invalid = Path.GetInvalidFileNameChars ();
			if (name.IndexOfAny (invalid) >= 0) {
				foreach (char c in invalid)
					name = name.Replace (c.ToString (), "");
				
				if (name == String.Empty)
					return null;
				return name;
			} else {
				return name;
			}
		}
	}
}